/*
    TwoPageView image viewer
    Copyright (C) 2006 Japan Manoretimedia Lab.

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/
using System;

namespace LhaUtil
{
    public enum LzhCompressMethodType
    {
        LH0,
        LH4,
        LH5,
        LH6,
        LH7,
        LZ4
    }

    /// <summary>
    /// This class represents an entry in a LHa archive.  This can be a file
    /// or a directory
    /// LhaInputStream will give you instances of this class as 
    /// information about the members in an archive.
    ///
    /// LhaEntry is based on ZipEntry.cs. 
    /// ZipEntry copyright is as follows.
    /// Copyright (C) 2001 Mike Krueger
    /// Copyright (C) 2004 John Reilly
    /// <br/>
    /// <br/>Author of the original java version : Jochen Hoenicke
    /// </summary>
    public class LhaEntry
    {
        public const long TooLargeFileSize = 256L * 1024 * 1024; //< max 256MB

        public LhaEntry(LhaInputStream stream)
        {
            byte[] bytes = new byte[258]; // byte0: headerSize, byte1: checksum
            stream.ReadRaw(bytes, 0, 21);
            int headerLevel = bytes[20];
            switch (headerLevel) {
            case 0:
            case 1:
                headerSize = bytes[0];
                if (0 == headerSize) {
                    return;
                }
                headerSize += 2; // headerSize(1) + crc(1)
                if (headerSize < 22) {
                    throw new System.IO.InvalidDataException();
                }
                stream.ReadRaw(bytes, 21, headerSize - 21);
                break;
            case 2:
                headerSize = Get2Le(bytes, 0);
                if (0 == headerSize) {
                    return; //< end of file
                }
                stream.ReadRaw(bytes, 21, 5);
                break;
            default:
                throw new System.NotSupportedException();
            }
            {
                System.Text.StringBuilder builder = new System.Text.StringBuilder(5);
                builder.Append((char)bytes[2]);
                builder.Append((char)bytes[3]);
                builder.Append((char)bytes[4]);
                builder.Append((char)bytes[5]);
                builder.Append((char)bytes[6]);
                string compressMethod = builder.ToString();
                switch (compressMethod) {
                case "-lh0-":
                    compressMethodType = LzhCompressMethodType.LH0;
                    dicbit = 0;
                    break;
                case "-lh1-":
                    throw new System.NotSupportedException();
                case "-lh2-":
                    throw new System.NotSupportedException();
                case "-lh3-":
                    throw new System.NotSupportedException();
                case "-lh4-":
                    compressMethodType = LzhCompressMethodType.LH4;
                    dicbit = 12;
                    break;
                case "-lh5-":
                default:
                    compressMethodType = LzhCompressMethodType.LH5;
                    dicbit = 13;
                    break;
                case "-lh6-":
                    compressMethodType = LzhCompressMethodType.LH6;
                    dicbit = 15;
                    break;
                case "-lh7-":
                    compressMethodType = LzhCompressMethodType.LH7;
                    dicbit = 16;
                    break;
                case "-lz4-":
                    compressMethodType = LzhCompressMethodType.LZ4;
                    dicbit = 0;
                    break;
                case "-lz5-":
                    throw new System.NotSupportedException();
                case "-lzs-":
                    throw new System.NotSupportedException();
                }
            }
            dicsize = (1 << dicbit);
            originalDataSize = Get4Le(bytes, 11);
            directoryName = "";
            fileName = "";
            if (0 == headerLevel || 1 == headerLevel) {
                int fileNameLen = bytes[21];
                if (headerSize - 24 < fileNameLen) {
                    fileNameLen = headerSize - 24;
                }
                if (0 < fileNameLen) {
                    for (int i = 0; i < fileNameLen; ++i) {
                        if (0xff == bytes[22 + i]) {
                            bytes[22 + i] = (byte)System.IO.Path.DirectorySeparatorChar;
                        }
                    }
                    System.Text.Encoding encoding = System.Text.Encoding.GetEncoding(932);
                    char[] chars = encoding.GetChars(bytes, 22, fileNameLen);
                    fileName = PathNameSanitize(new string(chars));
                }
            }
            packedDataSize = Get4Le(bytes, 7);
            if (TooLargeFileSize < originalDataSize || originalDataSize < 0
                ||TooLargeFileSize < packedDataSize || packedDataSize < 0) {
                originalDataSize = 0;
                packedDataSize = 0;
                headerSize = 0; //< file too large IMO. end of file
                //Console.WriteLine("E: LhaEntry() Filesize too large! {0} {1}", originalDataSize, packedDataSize);
                return;
            }

            if (1 == headerLevel) {
                for (ushort nextExtendedHeaderSize = Get2Le(bytes, headerSize - 2);
                     0 < nextExtendedHeaderSize;) {
                    packedDataSize -= nextExtendedHeaderSize;
                    nextExtendedHeaderSize = ReadExtendedHeader(stream, nextExtendedHeaderSize);
                }
            }
            if (2 == headerLevel) {
                for (ushort nextExtendedHeaderSize = Get2Le(bytes, 24);
                     0 < nextExtendedHeaderSize;
                     nextExtendedHeaderSize = ReadExtendedHeader(stream, nextExtendedHeaderSize)) {
                }
            }
        }

        private static ushort Get2Le(byte[] buffer, int offset)
        {
            return (ushort)(buffer[offset] +
                  (buffer[offset + 1] << 8));
        }

        private static int Get4Le(byte[] buffer, int offset)
        {
            return (int)(buffer[offset] +
                  (buffer[offset + 1] << 8) +
                  (buffer[offset + 2] << 16) +
                  (buffer[offset + 3] << 24));
        }
        
        /// <summary>
        /// read the next extended header
        /// </summary>
        /// <param name="stream">stream to read</param>
        /// <param name="extendedHeaderSize">this extended header size</param>
        /// <returns>next extended header size. 0 if it is not exist</returns>
        private ushort ReadExtendedHeader(LhaInputStream s, ushort extendedHeaderSize)
        {
            byte[] headerBuf = new byte[extendedHeaderSize];
            s.ReadRaw(headerBuf, 0, extendedHeaderSize);
            
            byte headerId = headerBuf[0];

            switch (headerId) {
            case 1: // file name header
                {
                    System.Text.Encoding encoding = System.Text.Encoding.GetEncoding(932);
                    char[] chars = encoding.GetChars(headerBuf, 1, extendedHeaderSize-3);
                    fileName = PathNameSanitize(new string(chars));
                }
                break;
            case 2: // directory name header
                {
                    for (int i = 0; i < extendedHeaderSize - 3; ++i) {
                        if (0xff == headerBuf[1 + i]) {
                            headerBuf[1 + i] = (byte)System.IO.Path.DirectorySeparatorChar;
                        }
                    }
                    System.Text.Encoding encoding = System.Text.Encoding.GetEncoding(932);
                    char[] chars = encoding.GetChars(headerBuf, 1, extendedHeaderSize - 3);
                    directoryName = PathNameSanitize(new string(chars));
                }
                break;
            default: 
                {
                    //Console.WriteLine("D: skipped header {0}", headerId);
                }
                break;
            }

            return Get2Le(headerBuf, extendedHeaderSize-2);
        }

        private static string PathNameSanitize(string path)
        {
            string up = string.Format(System.Globalization.CultureInfo.InvariantCulture, "..{0}", System.IO.Path.DirectorySeparatorChar);

            path = path.Replace(up, "");
            if (path[path.Length - 1] == System.IO.Path.DirectorySeparatorChar) {
                path = path.Remove(path.Length - 1);
            }
            if (2 <= path.Length && path[1] == ':') {
                path = path.Substring(2);
            }
            while (1 <= path.Length && path[0] == System.IO.Path.DirectorySeparatorChar) {
                path = path.Substring(1);
            }
            return path;
        }
#region instance fields
        private LzhCompressMethodType compressMethodType;
        public LzhCompressMethodType CompressMethod
        {
            get { return compressMethodType; }
        }
        private int headerSize;
        public int HeaderSize
        {
            get { return headerSize; }
        }
        private int packedDataSize;
        public int PackedDataSize
        {
            get { return packedDataSize; }
        }
        private int originalDataSize;
        public int OriginalDataSize
        {
            get { return originalDataSize; }
        }
        private int dicbit;
        public int Dicbit
        {
            get { return dicbit; }
        }
        private int dicsize;
        public int Dicsize
        {
            get { return dicsize; }
        }
        private bool decoded = false;
        public bool Decoded
        {
            get { return decoded; }
            set { decoded = value; }
        }
        private string directoryName;
        public string DirectoryName
        {
            get { return directoryName; }
        }
        private string fileName;
        public string FileName
        {
            get { return fileName; }
        }
        public string Name
        {
            get {
                if (0 == directoryName.Length) {
                    return fileName;
                }
                System.Text.StringBuilder builder = new System.Text.StringBuilder();
                builder.Append(directoryName);
                builder.Append(System.IO.Path.DirectorySeparatorChar);
                builder.Append(fileName);
                return builder.ToString();
            }
        }
#endregion
    }
}
